﻿// ===============================================================================
//  Copyright © NewSQL.CN. All rights reserved .
//  技术论坛：http://newsql.cn/
//  个人博客：http://www.ymind.net/
//  版权所有：NewSQL中文社区 彦铭工作室
// ===============================================================================

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;

namespace Newsql.Data.Configuration
{
	/// <summary>
	/// 数据库配置信息管理器。
	/// </summary>
	public static class DatabaseConfiguration
	{
		private static readonly object _asyncObject = new object();
		private static readonly Random _random = new Random();
		private static readonly SortedDictionary<string, SortedSet<DatabaseConnectionString>> _configurationItems = new SortedDictionary<string, SortedSet<DatabaseConnectionString>>();

		/// <summary>
		/// 初始化 <see cref="DatabaseConfiguration"/> 对象的值。
		/// </summary>
		static DatabaseConfiguration()
		{
			// 注册序列化器
			BsonSerializer.RegisterSerializer(typeof(SortedDictionary<string, SortedSet<DatabaseConnectionString>>), new DictionarySerializer<string, SortedSet<DatabaseConnectionString>>());
			BsonSerializer.RegisterSerializer(typeof(SortedSet<DatabaseConnectionString>), new EnumerableSerializer<DatabaseConnectionString>());
		}

		/// <summary>
		/// 获取存储数据库连接配置信息的物理路径。
		/// </summary>
		public static string DatabaseConfigFile { get; private set; }

		/// <summary>
		/// 获取 MongoDB 数据库连接配置文档的集合。
		/// </summary>
		public static SortedDictionary<string, SortedSet<DatabaseConnectionString>> ConfigurationItems
		{
			get
			{
				if (_configurationItems.Count == 0)
				{
					lock (_configurationItems)
					{
						_LoadConfigs(DatabaseConfigFile);
					}
				}

				return _configurationItems;
			}
		}

		/// <summary>
		/// 返回数据库连接配置信息。
		/// </summary>
		/// <param name="dbType">数据库类型，必须与配置文档中的描述保持一致。</param>
		/// <param name="groupName">分组名称。</param>
		/// <returns>如果找到至少一个匹配项则随机从中返回一个可用项，否则返回 null。</returns>
		public static string GetConnectionStrings(string dbType, string groupName)
		{
			var conn = _GetConnectionStrings(ConfigurationItems, dbType, groupName);

			if (conn.Equals(DatabaseConnectionString.Empty)) throw new Exception(string.Format("数据库连接初始化失败。{0}", groupName));

			return conn.Value;
		}

		/// <summary>
		/// 返回数据库连接配置信息。
		/// </summary>
		/// <param name="configurationItems">数据库连接配置信息的集合。</param>
		/// <param name="dbType">数据库类型，必须与配置文档中的描述保持一致。</param>
		/// <param name="groupName">分组名称。</param>
		/// <returns>如果找到至少一个匹配项则随机从中返回一个可用项，否则返回 null。</returns>
		private static DatabaseConnectionString _GetConnectionStrings(IDictionary<string, SortedSet<DatabaseConnectionString>> configurationItems, string dbType, string groupName)
		{
			if (configurationItems.Count == 0) return DatabaseConnectionString.Empty;

			if (String.IsNullOrEmpty(dbType) || configurationItems.ContainsKey(dbType) == false) return DatabaseConnectionString.Empty;
			if (String.IsNullOrEmpty(groupName)) return DatabaseConnectionString.Empty;

			var configItems = configurationItems[dbType];

			if (configItems.Count == 0) return DatabaseConnectionString.Empty;

			var connList = configItems.Where(item => item.Group == groupName).ToList();

			return connList[_random.Next(0, connList.Count)];
		}

		/// <summary>
		/// 从指定路径加载数据库连接配置信息。
		/// </summary>
		/// <param name="fileName">配置信息文档的的完整物理路径。</param>
		/// <param name="noCache">是否禁用缓存。</param>
		public static void Load(string fileName, bool noCache = false)
		{
			if (String.IsNullOrWhiteSpace(fileName)) throw new ArgumentOutOfRangeException("fileName");
			if (!File.Exists(fileName)) throw new FileNotFoundException("指定的配置文件不存在", fileName);

			if (!noCache && _configurationItems.Count != 0) return;

			DatabaseConfigFile = fileName;

			lock (_configurationItems)
			{
				_LoadConfigs(DatabaseConfigFile);
			}
		}

		/// <summary>
		/// 将配置信息保存到指定路径。
		/// </summary>
		/// <param name="fileName">配置信息文档的的完整物理路径。</param>
		public static void Save(string fileName = null)
		{
			if (String.IsNullOrWhiteSpace(fileName)) throw new ArgumentOutOfRangeException("fileName");

			using (var fileStream = new StreamWriter(fileName ?? DatabaseConfigFile, false, Encoding.UTF8))
			{
				var json = DataHelper.ToJson(_configurationItems, true);

				fileStream.Write(json);
			}
		}

		private static void _LoadConfigs(string fileName)
		{
			if (String.IsNullOrWhiteSpace(fileName)) throw new ArgumentOutOfRangeException("fileName");

			if (_configurationItems.Count > 0) return;

			lock (_asyncObject)
			{
				if (_configurationItems.Count > 0) return;

				var configurationFileInfo = new FileInfo(fileName);

				if (!configurationFileInfo.Exists || configurationFileInfo.Length < 5) return;

				var configPath = Path.GetDirectoryName(DatabaseConfigFile);

				if (String.IsNullOrEmpty(configPath)) return;
				if (Directory.Exists(configPath) == false) throw new DirectoryNotFoundException();

				using (var fileStream = new StreamReader(fileName, Encoding.UTF8))
				{
					var items = BsonSerializer.Deserialize<SortedDictionary<string, SortedSet<DatabaseConnectionString>>>(fileStream);

					if (items == null || items.Count == 0) return;

					_configurationItems.Clear();

					foreach (var item in items) _configurationItems.Add(item.Key, item.Value);
				}
			}
		}
	}
}
